/*============================================
 * filename :   HttpUtil
 * author   :   tangfan
 * date     :   2018/8/14
 * desc     :
 * version  :   1.0.0
 * Copyright (c) 2018-Junbo www.gzjunbo.net
==============================================*/

package com.baiyue.utils;

import com.fasterxml.jackson.databind.JavaType;
import com.baiyue.security.JunboAuthenticator;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.util.Timeout;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static ch.qos.logback.core.util.CloseUtil.closeQuietly;

/**
 * @author tangfan
 * @author tangfan 2019/4/10 09:42 新增针对不同的编码格式返回
 */
public class HttpUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
    private static final int CONECT_TIMEOUT = 30000;
    private static final int READ_TIMEOUT = 30000;

    /**
     * 获取输入流文本内容，使用指定的charset
     *
     * @param con
     * @param charset
     * @return
     * @throws IOException
     */
    private static StringBuilder getStringBuilder(HttpURLConnection con, Charset charset) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), charset))) {
            String line;
            while ((line = in.readLine()) != null) {
                content.append(line);
                content.append(System.lineSeparator());
            }
        } catch (IOException e) {
            String errorMsg = "";
            if (con.getErrorStream() != null) {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getErrorStream(), charset))) {
                    String line;
                    while ((line = in.readLine()) != null) {
                        content.append(line);
                        content.append(System.lineSeparator());
                    }
                    errorMsg = e.getMessage()+", 响应体："+ content;
                } catch (IOException ex) {
                    errorMsg = "读取 HTTP 错误流时出现异常: " + ex.getMessage();
                }
            } else {
                errorMsg = "读取 HTTP 错误流时出现异常: " + e.getMessage();
            }
            if (e instanceof SSLHandshakeException) {
                throw new SSLHandshakeException(errorMsg);
            }
            throw new IOException(errorMsg,e);
        }
        return content;
    }

    /**
     * 获取输入流文本内容，使用默认的encoding。该配置一般在file.encoding设置。如果未设置，将使用默认的utf-8
     *
     * @param con
     * @return
     * @throws IOException
     */
    private static StringBuilder getStringBuilder(HttpURLConnection con) throws IOException {
        return getStringBuilder(con, Charset.defaultCharset());
    }

    public static Map<String, Object> get(String url, boolean sslContent) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        return get(url, null, sslContent);
    }

    public static Map<String, Object> get(String url) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        return get(url, null, false);
    }

    /**
     * 发送一个GET请求
     *
     * @param url
     * @return
     * @throws MalformedURLException
     * @throws ProtocolException
     * @throws IOException
     */
    public static Map<String, Object> get(String url, Map<String, String> headers, Boolean sslContent) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        HttpURLConnection con = null;
        try {
            // 在此处判断是否需要忽略 SSL 证书验证
            if (sslContent) {
                // 创建一个信任所有证书的 TrustManager
                TrustManager[] trustAllCerts = new TrustManager[]{
                        new X509TrustManager() {
                            @Override
                            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                                return null;
                            }

                            @Override
                            public void checkClientTrusted(
                                    java.security.cert.X509Certificate[] certs, String authType) {
                            }

                            @Override
                            public void checkServerTrusted(
                                    java.security.cert.X509Certificate[] certs, String authType) {
                            }
                        }
                };
                // 获取默认的 SSLContext
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
                // 将 SSLContext 应用到 HttpsURLConnection
                HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

                // 忽略主机名验证
                HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
            }
            long startTime = System.currentTimeMillis();
            URL myUrl = new URL(url);
            con = (HttpURLConnection) myUrl.openConnection();
            con.setRequestMethod("GET");
            // 遍历 headers，设置请求头
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    con.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            StringBuilder content;
            content = getStringBuilder(con);
            // 记录请求结束时间
            long endTime = System.currentTimeMillis();
            Integer status = con.getResponseCode();
            System.out.println(status);
            Map<String, Object> result = new HashMap<>();
            // 计算请求耗时（单位：毫秒）
            long elapsedTime = endTime - startTime;
            result.put("elapsedTime", elapsedTime);
            result.put("status", status);
            result.put("content", content.toString());
            return result;
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
    }

    public static String formDataPost(String url, RequestBody requestBody, Map<String, String> headers) throws IOException {
        OkHttpClient client = new OkHttpClient().newBuilder()
                .connectTimeout(CONECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .readTimeout(READ_TIMEOUT,TimeUnit.MILLISECONDS)
                .build();
        Request.Builder requestBuilder = new Request.Builder()
                .url(url)
                .method("POST", requestBody);
        // 动态添加请求头
        if (headers != null) {
            headers.forEach(requestBuilder::addHeader);
        }
        Request request = requestBuilder
                .addHeader("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE)
                .build();
        try (Response response = client.newCall(request).execute();){
            if (response.isSuccessful() && response.body() != null) {
                return response.body().string();
            } else {
                throw new IOException("formDataPost异常返回: " + response);
            }
        }
    }

    public static Map<String, Object> post(String url, String urlParameters, String contentType, Map<String, String> headers) throws IOException {
        return post(url, urlParameters, contentType, Charset.defaultCharset(), headers, 0, 0);
    }

    public static Map<String, Object> patch(String url, String urlParameters, String contentType, Map<String, String> headers) throws IOException {
        return patch(url, urlParameters, contentType, Charset.defaultCharset(), headers, 0, 0);
    }

    public static Map<String, Object> post(String url, String urlParameters, String contentType, Charset charset, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
        byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
        HttpURLConnection con = null;
        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        try {
            URL myUrl = new URL(url);
            con = (HttpURLConnection) myUrl.openConnection();
            con.setUseCaches(false);
            con.setDoOutput(true);
            con.setRequestMethod("POST");
            con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36 Junbo/1.0.4 Junbo-Common/Tookit");
            con.setRequestProperty("Content-Type", contentType);
            if (connectTimeout != 0 || readTimeout != 0) {
                con.setConnectTimeout(connectTimeout);
                con.setReadTimeout(readTimeout);
            } else {
                con.setConnectTimeout(CONECT_TIMEOUT);
                con.setReadTimeout(READ_TIMEOUT);
            }
            logger.trace("http post:{}, param:{}, content-type:{}", url, urlParameters, contentType);
            boolean containOrigin = false;
            if (headers != null && headers.size() > 0) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    if (StringUtils.equalsIgnoreCase(entry.getKey(), "origin")) {
                        containOrigin = true;
                    }
                    con.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            if (!containOrigin) {
                con.setRequestProperty("Origin", WebUtil.getDomain());
            }
            try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) {
                wr.write(postData);
            }
            // 记录请求结束时间
            long endTime = System.currentTimeMillis();
            StringBuilder content = getStringBuilder(con, charset);
            Integer status = con.getResponseCode();
            logger.trace("http post:{}, response:{}, status:{}", url, content, status);
            Map<String, Object> result = new HashMap<>();
            // 计算请求耗时（单位：毫秒）
            long elapsedTime = endTime - startTime;
            result.put("elapsedTime", elapsedTime);
            result.put("status", status);
            result.put("content", content.toString());
            return result;
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
    }

    public static Map<String, Object> patch(String url, String urlParameters, String contentType, Charset charset, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        CloseableHttpResponse closeableHttpResponse;
        try {
            HttpPatch httpPatch = new HttpPatch(url);
            httpPatch.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36 Junbo/1.0.4 Junbo-Common/Tookit");
            httpPatch.addHeader("Content-Type", contentType);
            RequestConfig config = null;
            StringEntity stringEntity = new StringEntity(urlParameters, ContentType.create("text/json", "UTF-8"));
            httpPatch.setEntity(stringEntity);
            if (connectTimeout != 0 || readTimeout != 0) {
                config = RequestConfig.custom().setConnectTimeout(readTimeout)
                        .setSocketTimeout(connectTimeout)
                                .build();
            } else {
                config = RequestConfig.custom().setSocketTimeout(CONECT_TIMEOUT)
                        .setConnectTimeout(READ_TIMEOUT)
                        .build();
            }
            httpPatch.setConfig(config);
            boolean containOrigin = false;
            if (headers != null && headers.size() > 0) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    if (StringUtils.equalsIgnoreCase(entry.getKey(), "origin")) {
                        containOrigin = true;
                    }
                    httpPatch.addHeader(entry.getKey(), entry.getValue());
                }
            }
            if (!containOrigin) {
                httpPatch.addHeader("Origin", WebUtil.getDomain());
            }
            long startTime = System.currentTimeMillis();
            closeableHttpResponse = closeableHttpClient.execute(httpPatch);
            String responseContent = EntityUtils.toString(closeableHttpResponse.getEntity(), charset);
            int status = closeableHttpResponse.getStatusLine().getStatusCode();

            // 记录请求结束时间
            long endTime = System.currentTimeMillis();
            // 计算请求耗时（单位：毫秒）
            long elapsedTime = endTime - startTime;
            if (status != HttpStatus.SC_OK){
                String errorMessage = "Http status code is not 200,response:"+responseContent;
                throw new IOException(errorMessage);
            }
            Map<String, Object> result = new HashMap<>();
            result.put("elapsedTime", elapsedTime);
            result.put("status", status);
            result.put("content", responseContent);
            return result;
        }finally {
            if (closeableHttpClient != null){
                closeableHttpClient.close();
            }
        }
    }

    /**
     * 使用默认的urlencoded进行POST消息
     *
     * @param url
     * @param urlParameters
     * @param clz
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T> T post(String url, String urlParameters, Class<T> clz) throws IOException {
        return JsonMapperUtil.fromJson(post(url, urlParameters, MediaType.APPLICATION_FORM_URLENCODED_VALUE, null), clz);
    }

    /**
     * 使用指定的contentType及charset发送消息
     *
     * @param url
     * @param content
     * @param mediaType
     * @param charset
     * @param clz
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T> T post(String url, String content, MediaType mediaType, Charset charset, Class<T> clz) throws IOException {
        return JsonMapperUtil.fromJson(post(url, content, mediaType, charset), clz);
    }

    public static Map<String, Object> post(String url, String urlParameters, MediaType mediaType, Charset charset) throws IOException {
        MediaType contentType = new MediaType(mediaType.getType(), charset.toString());
        return post(url, urlParameters, contentType.toString(), charset, null, 0, 0);
    }

    /**
     * 使用默认的url_encoded形式发送POST请求
     *
     * @param url           地址
     * @param urlParameters 键值对 a=b&c=d
     * @return
     * @throws IOException
     */
    public static Map<String, Object> post(String url, String urlParameters) throws IOException {
        return post(url, urlParameters, MediaType.APPLICATION_FORM_URLENCODED_VALUE, null);
    }

    /**
     * 使用默认的url_encoded形式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     * @throws IOException
     */
    public static Map<String, Object> post(String url, Object param) throws IOException {
        return post(url, JsonMapperUtil.toUri(param));
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     */
    public static Map<String, Object> postByJson(String url, Object param) throws IOException {
        return postByJson(url, param, new HashMap<>());
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     */
    public static Map<String, Object> PatchByJson(String url, Object param) throws IOException {
        return PatchByJson(url, param, new HashMap<>());
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param headers 请求头
     * @return
     */
    public static Map<String, Object> postByJson(String url, Object param, Map<String, String> headers) throws IOException {
        String params = null;
        if (param != null) {
            if (param.getClass().getName().equals(String.class.getName())) {
                params = (String) param;
            } else {
                params = JsonMapperUtil.toJson(param);
            }
        }
        return post(url, Objects.requireNonNull(params), MediaType.APPLICATION_JSON_UTF8_VALUE, headers);
    }

    /**
     * 使用JSON格式发送PATCH请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param headers 请求头
     * @return
     */
    public static Map<String, Object> PatchByJson(String url, Object param, Map<String, String> headers) throws IOException {
        String params = null;
        if (param != null) {
            if (param.getClass().getName().equals(String.class.getName())) {
                params = (String) param;
            } else {
                params = JsonMapperUtil.toJson(param);
            }
        }
        return patch(url, Objects.requireNonNull(params), MediaType.APPLICATION_JSON_UTF8_VALUE, headers);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     */
    public static Map<String, Object> postByJson(String url, Object param, int connectTimeout, int readTimeout) throws IOException {
        return postByJson(url, param, new HashMap<>(), connectTimeout, readTimeout);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param headers 请求头
     * @return
     */
    public static Map<String, Object> postByJson(String url, Object param, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
        String params = null;
        if (param != null) {
            if (param.getClass().getName().equals(String.class.getName())) {
                params = (String) param;
            } else {
                params = JsonMapperUtil.toJson(param);
            }
        }
        return post(url, Objects.requireNonNull(params), MediaType.APPLICATION_JSON_UTF8_VALUE, headers, connectTimeout, readTimeout);
    }

    public static Map<String, Object> post(String url, String urlParameters, String contentType, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
        return post(url, urlParameters, contentType, Charset.defaultCharset(), headers, connectTimeout, readTimeout);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     */
    public static <T> T postByJson(String url, Object param, Class<T> clazz) throws IOException {
        return JsonMapperUtil.fromJson(postByJson(url, param), clazz);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param authKey 验证密钥 非加密密钥
     * @return
     */
    public static <T> T postByJsonSign(String url, Object param, String authKey, Class<T> clazz) throws IOException {
        Map<String, String> headers = new HashMap<>(1);
        headers.put("Sign", SecurityUtil.sign(param, String.valueOf(JunboAuthenticator.getAuthCode(authKey))));
        return JsonMapperUtil.fromJson(postByJson(url, param, headers), clazz);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param headers 请求头
     * @return
     */
    public static <T> T postByJson(String url, Object param, Map<String, String> headers, Class<T> clazz) throws IOException {
        return JsonMapperUtil.fromJson(postByJson(url, param, headers), clazz);
    }

    /**
     * 使用JSON格式发送POST请求
     *
     * @param url     请求地址
     * @param param   请求参数
     * @param headers 请求头
     * @return javaType
     */
    public static <T> T postByJson(String url, Object param, Map<String, String> headers, JavaType javaType) throws IOException {
        return JsonMapperUtil.fromJson(postByJson(url, param, headers), javaType);
    }
}
